
package com.wuhao.code.check

import com.intellij.application.options.CodeStyle
import com.intellij.codeHighlighting.HighlightDisplayLevel
import com.intellij.codeInsight.actions.LastRunReformatCodeOptionsProvider
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.highlighter.HtmlFileType
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.ide.util.PropertiesComponent
import com.intellij.json.JsonFileType
import com.intellij.json.JsonLanguage
import com.intellij.lang.Language
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.lang.html.HTMLLanguage
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.editor.colors.CodeInsightColors
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.profile.codeInspection.InspectionProfileManager
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider
import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingRule
import com.intellij.psi.codeStyle.arrangement.match.ArrangementSectionRule
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementEntryMatcher
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule
import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchCondition
import com.intellij.psi.codeStyle.arrangement.model.ArrangementCompositeMatchCondition
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementExtendableSettings
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementRuleAliasToken
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementSettings
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Grouping.*
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Modifier.*
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Order.*
import com.intellij.ui.JBColor
import com.wuhao.code.check.constants.DEFAULT_CONTINUATION_INDENT_SPACE_COUNT
import com.wuhao.code.check.constants.DEFAULT_INDENT_SPACE_COUNT
import com.wuhao.code.check.constants.InspectionNames
import com.wuhao.code.check.constants.InspectionNames.CODE_FORMAT
import com.wuhao.code.check.constants.InspectionNames.JAVA_COMMENT
import com.wuhao.code.check.constants.InspectionNames.JAVA_FORMAT
import com.wuhao.code.check.constants.InspectionNames.JAVA_PROPERTY_CLASS
import com.wuhao.code.check.constants.InspectionNames.KOTLIN_COMMENT
import com.wuhao.code.check.constants.InspectionNames.KOTLIN_FORMAT
import com.wuhao.code.check.constants.InspectionNames.MYBATIS
import com.wuhao.code.check.constants.InspectionNames.PROPERTY_CLASS
import com.wuhao.code.check.style.KotlinModifier.LATEINIT
import com.wuhao.code.check.style.KotlinModifier.OPEN
import com.wuhao.code.check.style.arrangement.JavaRearrangeRules
import com.wuhao.code.check.style.arrangement.KotlinRearrangeRules
import com.wuhao.code.check.style.arrangement.RuleDescription
import com.wuhao.code.check.template.KotlinTemplates
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import java.awt.Color

/**
 * 项目启动时运行，主要对代码格式的配置按公司规范进行重写
 * @author 吴昊
 * @since 1.2.6
 */
class PluginStart : ProjectActivity {

  companion object {

    const val CODE_FORMAT_SEVERITY_NAME: String = "Code Format"
  }

  override suspend fun execute(project: Project) {
    // 强制启用java代码重排和import重新组织的功能
    val settings = CodeStyle.getSettings(project)
    setRearrange(settings)
    // 设定代码缩进
    setIndent(settings)
    setTemplates(project)
    setSeverity(project)
    setDefaults(settings)
  }

  fun setKotlinDefaults(settings: CodeStyleSettings) {
    val kotlinStyleSettings = settings.getCustomSettings(KotlinCodeStyleSettings::class.java)
    kotlinStyleSettings.apply {
      val fields = kotlinStyleSettings.javaClass.fields.map { it.name }
      if (fields.contains("SPACE_BEFORE_WHEN_PARENTHESES")) {
        SPACE_BEFORE_WHEN_PARENTHESES = true
      }
      if (fields.contains("SPACE_AROUND_RANGE")) {
        SPACE_AROUND_RANGE = false
      }
      if (fields.contains("SPACE_BEFORE_EXTEND_COLON")) {
        SPACE_BEFORE_EXTEND_COLON = true
      }
      if (fields.contains("SPACE_AFTER_EXTEND_COLON")) {
        SPACE_AFTER_EXTEND_COLON = true
      }
      if (fields.contains("SPACE_BEFORE_TYPE_COLON")) {
        SPACE_BEFORE_TYPE_COLON = false
      }
      if (fields.contains("SPACE_AFTER_TYPE_COLON")) {
        SPACE_AFTER_TYPE_COLON = true
      }
      if (fields.contains("ALIGN_IN_COLUMNS_CASE_BRANCH")) {
        ALIGN_IN_COLUMNS_CASE_BRANCH = true
      }
      if (fields.contains("SPACE_AROUND_FUNCTION_TYPE_ARROW")) {
        SPACE_AROUND_FUNCTION_TYPE_ARROW = true
      }
      if (fields.contains("SPACE_AROUND_WHEN_ARROW")) {
        SPACE_AROUND_WHEN_ARROW = true
      }
      if (fields.contains("SPACE_BEFORE_LAMBDA_ARROW")) {
        SPACE_BEFORE_LAMBDA_ARROW = true
      }
      if (fields.contains("SPACE_BEFORE_WHEN_PARENTHESES")) {
        SPACE_BEFORE_WHEN_PARENTHESES = true
      }
      if (fields.contains("LBRACE_ON_NEXT_LINE")) {
        LBRACE_ON_NEXT_LINE = false
      }
      if (fields.contains("NAME_COUNT_TO_USE_STAR_IMPORT")) {
        NAME_COUNT_TO_USE_STAR_IMPORT = 5
      }
      if (fields.contains("NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS")) {
        NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS = 3
      }
      if (fields.contains("CONTINUATION_INDENT_IN_PARAMETER_LISTS")) {
        CONTINUATION_INDENT_IN_PARAMETER_LISTS = true
      }
      if (fields.contains("CONTINUATION_INDENT_IN_ARGUMENT_LISTS")) {
        CONTINUATION_INDENT_IN_ARGUMENT_LISTS = true
      }
      if (fields.contains("CONTINUATION_INDENT_FOR_EXPRESSION_BODIES")) {
        CONTINUATION_INDENT_FOR_EXPRESSION_BODIES = true
      }
      if (fields.contains("CONTINUATION_INDENT_FOR_CHAINED_CALLS")) {
        CONTINUATION_INDENT_FOR_CHAINED_CALLS = true
      }
      if (fields.contains("CONTINUATION_INDENT_IN_SUPERTYPE_LISTS")) {
        CONTINUATION_INDENT_IN_SUPERTYPE_LISTS = true
      }
      if (fields.contains("CONTINUATION_INDENT_IN_IF_CONDITIONS")) {
        CONTINUATION_INDENT_IN_IF_CONDITIONS = true
      }
    }
  }

  private fun createJavaSettings(): StdArrangementSettings {
    val groupingRules = listOf(
        ArrangementGroupingRule(GETTERS_AND_SETTERS, KEEP),
        ArrangementGroupingRule(OVERRIDDEN_METHODS, BY_NAME),
        ArrangementGroupingRule(DEPENDENT_METHODS, BREADTH_FIRST)
    )
    val sections = createSections(JavaRearrangeRules.get())
    val tokens = listOf(StdArrangementRuleAliasToken("visibility").apply {
      definitionRules = listOf(
          PUBLIC, PACKAGE_PRIVATE,
          PROTECTED, PRIVATE, LATEINIT
      ).map {
        StdArrangementMatchRule(
            StdArrangementEntryMatcher(ArrangementAtomMatchCondition(it))
        )
      }
    })
    return StdArrangementExtendableSettings(groupingRules, sections, tokens)
  }

  private fun createKotlinSettings(): StdArrangementSettings {
    val sections = createSections(KotlinRearrangeRules.get())
    val tokens = listOf(StdArrangementRuleAliasToken("visibility").apply {
      definitionRules = listOf(OPEN, PUBLIC, PACKAGE_PRIVATE, PROTECTED, PRIVATE, LATEINIT).map {
        StdArrangementMatchRule(
            StdArrangementEntryMatcher(ArrangementAtomMatchCondition(it))
        )
      }
    })
    return StdArrangementExtendableSettings(listOf(), sections, tokens)
  }

  private fun createMatcher(rule: RuleDescription): StdArrangementEntryMatcher {
    return StdArrangementEntryMatcher(
        ArrangementCompositeMatchCondition().apply {
          rule.template.forEach { token ->
            this.addOperand(ArrangementAtomMatchCondition(token))
          }
        }
    )
  }

  private fun createSections(rules: List<RuleDescription>): List<ArrangementSectionRule> {
    return rules.map { rule ->
      if (rule.order == null) {
        StdArrangementMatchRule(createMatcher(rule), BY_NAME)
      } else {
        StdArrangementMatchRule(createMatcher(rule), rule.order!!)
      }
    }.map {
      ArrangementSectionRule.create(it)
    }
  }

  private fun setDefaults(settings: CodeStyleSettings) {
    if (isIdea) {
      setKotlinDefaults(settings)
    }
  }

  private fun setIndent(settings: CodeStyleSettings) {
    val setIndentFileTypes = arrayListOf(
        JsonFileType.INSTANCE,
        XmlFileType.INSTANCE,
        HtmlFileType.INSTANCE
    )
    if (isIdea) {
      setIndentFileTypes.addAll(
          listOf(
              JavaFileType.INSTANCE,
              KotlinFileType.INSTANCE
          )
      )
    }
    setIndentFileTypes.forEach { fileType ->
      val language = when (fileType) {
        is JsonFileType -> JsonLanguage.INSTANCE
        is HtmlFileType -> HTMLLanguage.INSTANCE
        else            -> {
          if (isIdea) {
            when (fileType) {
              is KotlinFileType -> KotlinLanguage.INSTANCE
              is JavaFileType   -> JavaLanguage.INSTANCE
              else              -> null
            }
          } else {
            null
          }
        }
      }
      if (language != null) {
        setIndent(fileType, language, settings)
      }
    }
  }

  private fun setLanguageArrangeSettings(
      myLastRunSettings: LastRunReformatCodeOptionsProvider,
      settings: CodeStyleSettings,
      language: Language,
      createSettings: StdArrangementSettings
  ) {
    myLastRunSettings.saveRearrangeState(language, true)
    settings.getCommonSettings(language).apply {
      setArrangementSettings(createSettings)
    }
  }

  private fun setRearrange(settings: CodeStyleSettings) {
    val myLastRunSettings = LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance())
    myLastRunSettings.saveRearrangeCodeState(true)
    myLastRunSettings.saveOptimizeImportsState(true)
    if (isIdea) {
      setLanguageArrangeSettings(myLastRunSettings, settings, JavaLanguage.INSTANCE, createJavaSettings())
      setLanguageArrangeSettings(myLastRunSettings, settings, KotlinLanguage.INSTANCE, createKotlinSettings())
    }
  }

  private fun setSeverity(project: Project) {
    val severityRegistrar = SeverityRegistrar.getSeverityRegistrar(project)
    val color = Color(255, 227, 96)
    severityRegistrar.registerSeverity(
        SeverityRegistrar.SeverityBasedTextAttributes(
            TextAttributes().apply {
              this.foregroundColor = JBColor.BLACK
              this.backgroundColor = color
              this.errorStripeColor = color
            },
            HighlightInfoType.HighlightInfoTypeImpl(
                HighlightSeverity(CODE_FORMAT_SEVERITY_NAME, 350),
                CodeInsightColors.WARNINGS_ATTRIBUTES
            )
        ), color
    )
    val severity = severityRegistrar.getSeverity(CODE_FORMAT_SEVERITY_NAME)
    val inspectionProfile = InspectionProfileManager.getInstance(project)
        .currentProfile
    InspectionNames.values().forEach {
      if (isIdea || it !in listOf(
              CODE_FORMAT, JAVA_COMMENT, JAVA_FORMAT, KOTLIN_COMMENT,
              KOTLIN_FORMAT, PROPERTY_CLASS, JAVA_PROPERTY_CLASS, MYBATIS
          )
      ) {
        inspectionProfile.enableTool(it.shortName, project)
        val tools = inspectionProfile.getTools(it.shortName, project)
        tools.level = HighlightDisplayLevel(severity!!)
      }

    }
  }

  private fun setTemplates(project: Project) {
    if (isIdea) {
      val fileTemplateManager = FileTemplateManager.getInstance(project)
      fileTemplateManager.apply {
        getInternalTemplate("Kotlin File").text = KotlinTemplates.FILE
        getInternalTemplate("Kotlin Class").text = KotlinTemplates.CLASS
        getInternalTemplate("Kotlin Enum").text = KotlinTemplates.ENUM
        getInternalTemplate("Kotlin Interface").text = KotlinTemplates.INTERFACE
      }
    }
  }

}

fun setIndent(fileType: FileType, language: Language?, settings: CodeStyleSettings) {
  settings.getIndentOptions(fileType).apply {
    INDENT_SIZE = DEFAULT_INDENT_SPACE_COUNT
    CONTINUATION_INDENT_SIZE = DEFAULT_CONTINUATION_INDENT_SPACE_COUNT
    TAB_SIZE = DEFAULT_INDENT_SPACE_COUNT
    USE_TAB_CHARACTER = false
  }
  if (language != null) {
    LanguageCodeStyleSettingsProvider.getDefaultCommonSettings(language)?.LINE_COMMENT_AT_FIRST_COLUMN = true
  }
}